﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using CoreLib.Communication.Packets;
using System.Collections.Concurrent;
using CoreLib.Communication.Cryptography;

namespace CoreLib.Communication.Sockets
{
    public class SocketClient
    {
        /// <summary>
        /// Gets the Socket that is being used by this SocketClient instance
        /// </summary>
        public Socket Socket { get; private set; }

        /// <summary>
        /// Returns true if the Socket is Connected, otherwise, false
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return (Socket != null && Socket.Connected && !DisconnectCalled);
            }
        }

        /// <summary>
        /// Gets or sets the cryptography used
        /// </summary>
        public ICipher Cryptography { get; set; }

        /// <summary>
        /// Object that can be recovered after an asynchornous execution
        /// </summary>
        public object Owner;

        // The socket server that created this SocketClient instance
        private SocketServer socketServer;
        // The byte[] that will hold the data received
        private byte[] receiveBuffer;
        // To stop calling recursively Socket.BeginReceive method
        private bool DisconnectCalled = false;

        /// <summary>
        /// Create a new instance of the SocketClient class
        /// </summary>
        /// <param name="newSocket">Socket that will be wrapped</param>
        /// <param name="socketServer">SocketServer that created the connection</param>
        public SocketClient(Socket newSocket, SocketServer socketServer)
        {
            this.Socket = newSocket;
            this.socketServer = socketServer;
            this.receiveBuffer = new byte[Packet.MaxSize];
            this.socketServer.Connected(this);
            this.Socket.BeginReceive(receiveBuffer, 0, Packet.MaxSize, SocketFlags.None, Receive_Callback, null); 
        }

        // Callback for the Socket.BeginReceive method
        private void Receive_Callback(IAsyncResult result)
        {
            try
            {
                int receivedSize;
                SocketError transferResult;
                receivedSize = Socket.EndReceive(result, out transferResult);
                if (receivedSize > 0 && transferResult == SocketError.Success)
                {

                    Packet stream = PacketRecycler.Pop(receivedSize);
                    if (Cryptography != null)
                        lock (Cryptography)
                            Cryptography.Decrypt(receiveBuffer, receivedSize);
                    stream.WriteByteArray(receiveBuffer, receivedSize);
                    stream.Seek(0, Origin.Begin);
                    socketServer.Received(this, stream);
                }
                else if (transferResult != SocketError.Success)
                    Disconnect();

                if (IsConnected)
                    Socket.BeginReceive(receiveBuffer, 0, Packet.MaxSize, SocketFlags.None, Receive_Callback, null);
            }
            catch (Exception e)
            {
                Disconnect();
            }
        }
        /// <summary>
        /// Disposes the socket used by the client and call the Disconnected event
        /// </summary>
        public void Disconnect()
        {
            try
            {
                this.DisconnectCalled = true;
                this.Socket.Close();
                this.Socket.Dispose();
            }
            finally
            {
                socketServer.Disconnected(this);
            }
        }
    }
}
